#include <algorithm>
#include <QTableView>
#include <QProgressDialog>
#include <QApplication>
#include <QMessageBox>
#include <QSettings>
#include <QInputDialog>
#include <QFileDialog>
#include <QDateTime>
#include <QStandardPaths>
#include <QTemporaryFile>

#include "framemodel.h"
#include "diutils.h"

#define BUFFER_SIZE 0x10000

FrameModel::FrameModel(const QString& fileName, const QByteArray& key, QObject *parent) :
    ProtocolTableModel(parent), maxColumns(0), firstPos(-1), searchPos(0), cacheRow(-1)
{
    file.setFileName(fileName);
    code = key;
//    firstPos = indexOf(0);
}

qint64 FrameModel::position(const QModelIndex &index)
{
    int row = index.row();
    int col = index.column();
    qint64 pos = firstPos;
    if (row > 0) {
        pos = records.at(row -1);
    }

    if (col < 2)
        return pos;
    else
        return pos + col - 1;
}

QModelIndex FrameModel::locate(qint64 pos)
{
    QVector<qint64>::iterator it = std::upper_bound(records.begin(), records.end(), pos);
    int row = std::distance(records.begin(), it);
    qint64 record = row == 0 ? firstPos : *(--it);
    int col = pos - record;

    return index(row, col + 1);
}

bool FrameModel::deleteRows(const QString &fileName, const QModelIndexList &rows)
{
    if (!calcRecords()) {
        return false;
    }

    if (!file.open(QIODevice::ReadOnly))
        return false;
    QFileGuard fg1(file);

    QFile out(fileName);
    if (!out.open(QIODevice::ReadWrite))
        return false;
    QFileGuard fg2(out);

    QProgressDialog progress(tr("File Saving ..."), tr("Cancel"), 0, records.size(), qApp->activeWindow()/*, Qt::WindowStaysOnTopHint*/);
    progress.setWindowTitle(tr("Progress"));
    progress.setModal(true);

    QMap<int, QModelIndex> delRows;
    for (QModelIndexList::const_iterator it = rows.begin(); it != rows.end(); it++) {
        QModelIndex index = *it;
        delRows.insert(index.row(), index);
    }
    progress.setMaximum(records.size());
    int step = 0;
    qint64 pos = firstPos;
    file.seek(firstPos);
    for (QVector<qint64>::iterator it = records.begin(); it != records.end(); it++) {
        qint64 record = *it;
        QByteArray data = file.read(record - pos);
        pos = record;
        if (!delRows.contains(step)) {
            out.write(data);
        }
        progress.setValue(++step);
        qApp->processEvents();
        if (progress.wasCanceled())
            return false;
    }

    return true;
}

bool FrameModel::deleteColumns(const QString &fileName, const QModelIndexList &cols)
{
    if (!calcRecords()) {
        return false;
    }

    if (!file.open(QIODevice::ReadOnly))
        return false;
    QFileGuard fg1(file);

    QFile out(fileName);
    if (!out.open(QIODevice::ReadWrite))
        return false;
    QFileGuard fg2(out);

    QMap<int, QModelIndex> delCols;
    for (QModelIndexList::const_iterator it = cols.begin(); it != cols.end(); it++)  {
        QModelIndex index = *it;
        delCols.insert(index.column(), index);
    }

    if (delCols.size() > 11)
        return false;

    QProgressDialog progress(tr("File Saving ..."), tr("Cancel"), 0, records.size(), qApp->activeWindow(), Qt::WindowStaysOnTopHint);
    progress.setWindowTitle(tr("Progress"));
    progress.show();

    int step = 0;
    QByteArray data = file.read(firstPos);
    for (int i = 0; i < data.size(); i++) {
        out.write(data.mid(i, 1));
    }
//    qint64 pos =0;
    qint64 pos = firstPos;
    file.seek(firstPos);
    for (QVector<qint64>::iterator it = records.begin(); it != records.end(); it++) {
        qint64 record = *it;
        QByteArray data = file.read(record - pos);
        pos = record;
        for (int i = 0; i < data.size(); i++) {
            if (delCols.contains(i+1))
                continue;
            out.write(data.mid(i, 1));
        }

        progress.setValue(++step);
        qApp->processEvents();
        if (progress.wasCanceled())
            return false;
        pos = *it;
    }

    return true;
}

bool FrameModel::saveRows(const QString &fileName, const QModelIndexList &rows)
{
    if (!file.open(QIODevice::ReadOnly))
        return false;
    QFileGuard fg1(file);

    QFile out(fileName);
    if (!out.open(QIODevice::ReadWrite))
        return false;
    QFileGuard fg2(out);

    QProgressDialog progress(tr("Save seleted rows as..."), tr("Cancel"), 0, rows.size(), qApp->activeWindow());
    progress.setWindowTitle(tr("Progress"));
    progress.setModal(true);
    int step = 0;
    for (QModelIndexList::const_iterator it = rows.begin(); it != rows.end(); it++) {
        QModelIndex index = *it;
        int row = index.row();
        qint64 from = row > 0 ? records.at(row - 1) : 0;
        file.seek(from);
        qint64 size = row > 0 ? records.at(row) - records.at(row - 1) : records.at(row);
        out.write(file.read(size));
        progress.setValue(++step);
        qApp->processEvents();
        if (progress.wasCanceled())
            return false;
    }

    return true;
}

bool FrameModel::saveColumns(const QString &fileName, const QModelIndexList &cols)
{
    if (!calcRecords()) {
        return false;
    }

    if (!file.open(QIODevice::ReadOnly))
        return false;
    QFileGuard fg1(file);

    QFile out(fileName);
    if (!out.open(QIODevice::ReadWrite))
        return false;
    QFileGuard fg2(out);

    QMap<int, QModelIndex> selectCols;
    for (QModelIndexList::const_iterator it = cols.begin(); it != cols.end(); it++)  {
        QModelIndex index = *it;
        selectCols.insert(index.column(), index);
    }
    QProgressDialog progress(tr("Save selected colnums as..."), tr("Cancel"), 0, records.size(), qApp->activeWindow());
    progress.setWindowTitle(tr("Progress"));
    progress.setModal(true);

    int step = 0;
    qint64 pos = 0;
    for (QVector<qint64>::iterator it = records.begin(); it != records.end(); it++) {
        qint64 record = *it;
        QByteArray data = file.read(record - pos);
        pos = record;
        for (int i = 0; i < data.size(); i++) {
            if (selectCols.contains(i))
                out.write(data.mid(i, 1));
        }

        progress.setValue(++step);
        qApp->processEvents();
        if (progress.wasCanceled())
            return false;
    }

    return true;
}

void FrameModel::filter(const QModelIndex &index)
{
    switch (index.column()) {
    case 0:
        filterSize(index);
        break;
    default:
        break;
    }
}

void FrameModel::loadRecord(int row) const
{
    if (row == cacheRow)
        return;

    if (file.open(QIODevice::ReadOnly)) {
        qint64 pos = row > 0 ? records.at(row - 1) : firstPos;
        file.seek(pos);
        recordSize = records.at(row) - pos;
        int displaySize = maxDisplaySize();
        if (recordSize <= displaySize)
            record = file.read(recordSize);
        else
            record = file.read(displaySize);

        file.close();

//        if (maxColumns < record.size()) {
//            maxColumns = record.size();
//        }
    }

    cacheRow = row;
}

bool FrameModel::calcRecords()
{
//    qint64 fileSize = file.size();
//    while (searchPos < fileSize) {
//        qint64 nextPos = forwardFind(file, code, searchPos);
//        if (nextPos > 0) {
//            records.push_back(nextPos);
//            searchPos = nextPos + code.size();
//        } else {
//            break;
//        }
//    }

//    return true;

    quint16 len = 0;
    qint64 size = file.size();
    if (size > records.last()) {
        qint64 pos = records.isEmpty() ? firstPos : records.last();
        if (file.open(QIODevice::ReadOnly)) {
            QProgressDialog progress(tr("Calculate all records: "), tr("Cancel"), pos, size, qApp->activeWindow());
            progress.setWindowTitle(tr("Progress"));
            progress.setModal(true);

            file.seek(pos);
            QDataStream is(&file);
            is.setByteOrder(QDataStream::LittleEndian);
            while (size - file.pos() >= 32) {
                is.skipRawData(30);
                is >> len;
                is.skipRawData(len);
                records.push_back(file.pos());
                progress.setValue(file.pos());
                qApp->processEvents();
                if (progress.wasCanceled()) {
                    file.close();
                    return false;
                }
            }
            file.close();
        }
    }

    return true;
}

int FrameModel::maxDisplaySize() const
{
    QSettings settings;
    int size = settings.value("Framing/maxDisplaySize", 0).toInt();
    if (0 == size) {
        size = 1024;
        settings.setValue("Framing/maxDisplaySize", size);
    }
    return size;
}

void FrameModel::filterSize(const QModelIndex &index)
{
    QInputDialog input;
    input.setInputMode(QInputDialog::IntInput);
    input.setWindowTitle(tr("Filter"));
    input.setLabelText(tr("Bytes"));
    input.setOkButtonText(tr("OK"));
    input.setCancelButtonText(tr("Cancel"));
    input.setIntMaximum(INT_MAX);
    input.setIntValue(data(index, Qt::DisplayRole).toInt());
    if (!input.exec())
        return;

    qint64 bytes = input.intValue();
    if (bytes <= 0)
        return;

    if (!calcRecords())
        return;

    QFileGuard fg(file, QFile::ReadOnly);
    if (!fg.isOpened())
        return;

    QTemporaryFile out(QDir::temp().filePath(QFileInfo(file).fileName()));
    out.setAutoRemove(false);
    if (out.open()) {
        qint64 from = firstPos;
        for (QVector<qint64>::const_iterator it = records.cbegin(); it != records.cend(); it++) {
            if (*it - from == bytes) {
                file.seek(from);
                out.write(file.read(bytes));
            }
            from = *it;
        }
        out.close();
        emit openTempFile(out.fileName());
    }
}

int FrameModel::rowCount(const QModelIndex &parent) const
{
    return parent.isValid() ? 0 : records.size();
}

int FrameModel::columnCount(const QModelIndex &parent) const
{
    return parent.isValid() ? 0 : maxColumns + 1;
}

QVariant FrameModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (role == Qt::DisplayRole) {
        int row = index.row();
        int col = index.column();
        if (row >= records.size() || row < 0)
            return QVariant();

        loadRecord(row);
        if (0 == col)
            return recordSize;
        else
            return toHex(record.mid(col - 1, 1), 0);
    }

    return QVariant();
}

void FrameModel::fetchMore(const QModelIndex &parent)
{
    if (parent.isValid())
        return;

    int newColumns = 0;
    int count = 0;
    qint64 fileSize = file.size();

    int oldSize = records.size();
    while (searchPos < fileSize) {
        qint64 nextPos = forwardFind(file, code, searchPos);
        if (nextPos >= 0) {
            if (firstPos < 0) {
                firstPos = nextPos;
                searchPos = firstPos + code.size();
                continue;
            }

            int columns = nextPos - searchPos + code.size();
            if (columns > newColumns)
                newColumns = columns;

            records.push_back(nextPos);
            searchPos = nextPos + code.size();

            if (++count >= 100)
                break;
        } else {
            QMessageBox::information(qApp->activeWindow(), tr("Information"), tr("Key code search finished."));
            searchPos = fileSize;
            if (!records.isEmpty()) {
                records.append(fileSize);
                count++;
            }
            break;
        }
    }

    if (count <= 0)
        return;

    beginInsertRows(QModelIndex(), oldSize, records.size() - 1);
    endInsertRows();


    int displaySize = maxDisplaySize();
    if (newColumns > displaySize)
        newColumns = displaySize;

    if (maxColumns < newColumns) {
        beginInsertColumns(QModelIndex(), maxColumns, newColumns - 1);
        maxColumns = newColumns;
        endInsertColumns();
    }
}

bool FrameModel::canFetchMore(const QModelIndex &parent) const
{
    if (parent.isValid() || searchPos >= file.size()) {
        return false;
    }

    return true;
}

QVariant FrameModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role == Qt::DisplayRole) {
        if (orientation == Qt::Horizontal) {
            if (section == 0)
                return tr("Bytes");
            else
                return section;
        } else {
            return section + 1;
        }
    }

    return QVariant();
}
